package com.yy.device.boot.handler;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.HexUtil;
import com.yy.util.BitConverter;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ReferenceCountUtil;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ServerDecoder extends ByteToMessageDecoder {

    boolean needCheckSum;
    /*
     *  记录帧头信息
     *  key: EB  value : <82, 86>
     */
    Map<Byte, Set<Byte>> headerBytes = new HashMap<>();

    byte[] delimiter = "\r\n".getBytes();

    public ServerDecoder(Character[] headers) {
        Assert.notEmpty(headers);
        this.needCheckSum = needCheckSum;
        // 此处会有些问题，这个实现不能支持 EB90， EC80的这种场景
        for (Character character : headers) {
            byte[] header = BitConverter.getBigBytes(character);
            if (headerBytes.containsKey(header[0])) {
                headerBytes.get(header[0]).add(header[1]);
            } else {
                Set<Byte> value = new HashSet<>();
                value.add(header[1]);
                headerBytes.put(header[0], value);
            }
        }
    }

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
        /*
            根据目前的协议，帧头段为两个字节、数据长度为两个字节、校验和为两个字节
            数据长度为从帧头到校验和的字节长度
            结尾\r\n
        */
        if (in.readableBytes() < 5) {
            // 当可读数据不满足协议基本要求时返回组包 14为基础协议和结尾的总长度
            return;
        }
        in.markReaderIndex();
        byte[] headBytes = new byte[2];
        // 匹配头中的开始标记 两个字节 ： 方法为从前向后依次成组的匹配与headBytes相同字节数组的内容，当出现任意字节不匹配时重新匹配
        while (in.isReadable()) {
            int i = 0;
            byte bit = in.readByte();
            headBytes[i] = bit;
            if (!headerBytes.containsKey(bit)) {
                in.markReaderIndex();
                continue;
            }
            i++;
            byte bit1 = in.readByte();
            headBytes[i] = bit1;
            // 两种帧头（0xEB82，0xEB86）
            if (!headerBytes.get(bit).contains(bit1)) {
                in.markReaderIndex();
                continue;
            }
            i++;
            break;
        }
        if (!in.isReadable()) {
            /*总包长度足够，但是可读范围内有丢包，直到后续才解析到帧头，此处返回，等待组包*/
            return;
        }
        if (in.readableBytes() < 3) {
            // 当可读数据不满足除帧头外的协议要求时重置并返回组包 6为基础协议和结尾减去帧头的长度 帧头验证通过，
            // 但是可读数据不满足除帧头外的协议要求,等待组包
            in.resetReaderIndex();
            return;
        }
        // 匹配长度 两个字节表示 得到从帧头到校验和的字节长度
        int dataLength = in.readShortLE();
        if (in.readableBytes() < dataLength - 4) {
            // 当可读数据不足协议中数据长度要求时重置并返回组包  2为基本协议长度-帧头-长度字段-结尾的长度 数据长度字段成功读取，
            // 但是可读数据不足协议中数据长度要求,等待组包
            in.resetReaderIndex();
            return;
        }
        // 进行到此处 说明协议的基本要求已经满足，那么当校验和 或者 结尾不正确时 该包需要丢弃 ，并且在最后一步把该包的所有信息都记录在日志里
        // 读除帧头、数据长度、校验和、结尾以外的数据部分，当校验和与结尾验证成功时这部分数据做为每个channel read重置读取索引
        in.resetReaderIndex();
        if (needCheckSum) {
            // 将除了校验和、结尾以外的部分读出来
            ByteBuf body = in.readSlice(dataLength - 2).retain(); // retain会导致in增加引用计数
            body.markReaderIndex();
            byte[] allBytes = new byte[body.readableBytes()];
            body.readBytes(allBytes);
            byte[] result = checksum(allBytes);
            byte check0 = in.readByte();
            byte check1 = in.readByte();
            // 验证校验和是否正确
            if (result[0] == check0 && result[1] == check1) {
                body.resetReaderIndex();
                out.add(body);
                in.readChar(); // 把结尾读出来
            } else {
                //校验和错误  帧头丢弃
                in.resetReaderIndex();
                in.readChar();
                // 报文解析失败需要释放body，否则in的引用计数会大于1，最终会导致直接内存泄露
                ReferenceCountUtil.release(body);
            }
        } else {
            // 将除了结尾以外的部分读出来
            ByteBuf body = in.readSlice(dataLength - 1).retain(); // retain会导致in增加引用计数
            body.markReaderIndex();
            byte[] allBytes = new byte[body.readableBytes()];
            body.readBytes(allBytes);
            int delimiter = in.readByte() & 0xff;
            // 检验结尾是否正确
            if (HexUtil.hexToInt("ED") == delimiter) {
                body.resetReaderIndex();
                out.add(body);
            } else {
                //校验和错误，帧头丢弃
                in.resetReaderIndex();
                in.readChar();
                // 报文解析失败需要释放body，否则in的引用计数会大于1，最终会导致直接内存泄露
                ReferenceCountUtil.release(body);
            }
        }

    }

    private boolean checkLengthUnAvailable(int dataLength) {
        if (dataLength < 16 || dataLength > 1024) {
            return true;
        }
        return false;
    }

    /**
     * 校验和计算
     *
     * @param checkData
     * @return
     * @author finrila
     */
    private static byte[] checksum(byte[]... checkData) {
        int sum = 0;
        for (int i = 0; i < checkData.length; i++) {
            for (int j = 0; j < checkData[i].length; j++) {
                sum += checkData[i][j] & 0xff;
            }
        }
        sum = ~sum;
        byte[] sumBytes = BitConverter.getBytes(sum);
        return new byte[]{sumBytes[0], sumBytes[1]};
    }

    public static void main(String[] args) {
        int ed = HexUtil.hexToInt("ED");
        System.out.println(ed);
    }
}
